/*
 * FileSystem.java
 *
 * Copyright (C) 2005-2008 Tommi Laukkanen
 * http://www.substanceofcode.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.substanceofcode.data.midp;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotFoundException;
import javax.microedition.rms.RecordStoreNotOpenException;

import com.substanceofcode.data.FileIOException;
import com.substanceofcode.data.FileSystem;
import com.substanceofcode.data.Serializable;

//import com.substanceofcode.tracker.view.Logger;
//import com.substanceofcode.localization.LocaleManager;
import net.sf.microlog.core.*;

/**
 * <p>A file-system overlay for the RMS</p>
 *
 * This class allows 'files' to be written to the RMS, and retrieved via a 'filename'.<br>
 * A 'file' can be any class which implements the Serializable interface.<br>
 * This class deals with all the issues of space, and spliting up files into smaller chunks,
 * making sure there is room in the RMS etc.<br>
 * <br>
 * Information stored in the FILE INDEX will be as follows:<br>
 * <ul>
 *   <li>Number of files (int)
 *   <li>Current RecordStore Number.
 *   <li>Number of records stored in current RecordStore.
 *   <li>File information[S]
 *   <ul>
 *     <li>File name (UTF)
 *     <li>File location (Serialized File locator)
 *   </ul>
 * </ul>
 *
 * @author Barry
 */
public class FileSystemImpl implements FileSystem
{

    /*
     * PUBLIC METHODS
     */

    public static FileSystemImpl getFileSystem()
    {
        if( iFileSystem == null )
        {
            LOG = LoggerFactory.getLogger();
            iFileSystem = new FileSystemImpl();
        }
        return iFileSystem;
    }

    /**
     * Needed for e.g. running inside Applets where static data isn't always
     * properly re-initialized.
     * 
     * Also needed for testing to ensure we simulate things being read back from 
     * flash rather than relying on in-memory hashtables, etc.
     */
    public static void resetStaticData()
    {
        iFileSystem = null;
    }

    public void setMaxRecordSize( int aMaxRecordSize )
    {
        iMaxRecordSize = aMaxRecordSize;
    }

    public void setMaxRecordsPerRecordStore( int aMaxRecordsPerRecordStore )
    {
        iMaxRecordsPerRecordStore = aMaxRecordsPerRecordStore;
    }

    public void setMaxRecordStores( int aMaxRecordStores )
    {
        iMaxRecordStores = aMaxRecordStores;
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#saveFile(java.lang.String, com.substanceofcode.data.Serializable, boolean)
     */
    public void saveFile( String aFilename, Serializable aFile, boolean aOverwrite ) throws FileIOException
    {
        saveFile( aFilename, aFile.getMimeType(), aFile, aOverwrite );
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#saveFile(java.lang.String, java.lang.String, com.substanceofcode.data.Serializable, boolean)
     */
    public void saveFile( String aFilename, String aMimeType, Serializable aFile, boolean aOverwrite ) throws FileIOException
    {
        if( iFileLocatorTable.containsKey( aFilename ) )
        {
            if( !aOverwrite )
            {
                throw new FileIOException( "file_system_fileioexception_already_exists" + aFilename );
            }
            else
            {
                deleteFile( aFilename );
            }
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream( baos );
        byte[] data = null;
        try
        {
            aFile.serialize( dos );
            data = baos.toByteArray();
            dos.close();
            baos.close();
        }
        catch( IOException e1 )
        {
            LOG.info( "file_system_fileioexception_serializing", e1 );
            throw new FileIOException( "file_system_fileioexception_serializing" + ":- " + e1.getMessage() );
        }
        int bytesWritten = 0;
        //If data is empty, numRecords is 0, then recordStores is 0 which
        //creates an  ArrayOutOfBounds error when reading or deleting the file...
        if( data.length == 0 )
        {
            LOG.info( "FileSystem: attempting to write a zero length file" );
            throw new FileIOException( "FileSystem: attempting to write a zero length file" );
        }
        int numRecords = data.length / iMaxRecordSize + ( data.length % iMaxRecordSize == 0 ? 0 : 1 );

        try
        {
            int[] recordStores = new int[numRecords];
            int[] records = new int[numRecords];
            RecordStore rs = null;
            try
            {
                rs = RecordStore.openRecordStore( RSFS_FILE_PREFIX + iCurrentRecordStoreNumber, true );
                for( int i = 0; i < numRecords; i++ )
                {

                    if( rs.getNumRecords() >= iMaxRecordsPerRecordStore )
                    {
                        rs.closeRecordStore();
                        iCurrentRecordStoreNumber++;
                        if( iCurrentRecordStoreNumber > iMaxRecordStores )
                        {
                            throw new FileIOException( "Out of space, max number of RecordStores used, unable to save " + aFilename );
                        }
                        rs = RecordStore.openRecordStore( RSFS_FILE_PREFIX + iCurrentRecordStoreNumber, true );
                    }

                    recordStores[i] = iCurrentRecordStoreNumber;

                    int bytesToWrite = i >= data.length / iMaxRecordSize ? data.length % iMaxRecordSize : iMaxRecordSize;
                    records[i] = rs.addRecord( data, bytesWritten, bytesToWrite );
                    bytesWritten += bytesToWrite;
                }
            }
            catch( FileIOException fioe )
            {
                LOG.info( "FileSystem saveFile error ", fioe );
                throw fioe;
            }
            catch( Exception e )
            {
                LOG.info( "FileSystem saveFile error ", e );
                throw new FileIOException( "FileSystem saveFile error: " + e.getMessage() );
            }
            finally
            {
                rs.closeRecordStore();
            }

            FileLocator fileLocator = new FileLocator( aFilename, aMimeType, data.length, recordStores, records );

            // Now try to commit the FileLocator to flash.
            // Do this as a separate step so we can roll-back properly.
            try
            {
                fileLocator = new FileLocator( aFilename, aMimeType, data.length, recordStores, records );
                iFileLocatorTable.put( aFilename, fileLocator );
                writeFileTableToRMS();
            }
            catch( Exception e )
            {
                LOG.info( "FileSystem saveFile error ", e );

                // Try to roll-back.

                iFileLocatorTable.remove( aFilename );

                deleteFileLocatorContents( fileLocator );

                throw new FileIOException( "FileSystem saveFile error adding FileLocator: " + e.getMessage() );
            }

        }
        catch( FileIOException fioe )
        {
            throw fioe;
        }
        catch( Exception e )
        {
            throw new FileIOException( "FileSystem saveFile error: " + e.getMessage() );
        }

    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#getFile(java.lang.String)
     */
    public DataInputStream getFile( String aFilename ) throws FileIOException
    {
        return new DataInputStream( new ByteArrayInputStream( getFileBytes( aFilename ) ) );
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#getFileBytes(java.lang.String)
     */
    public byte[] getFileBytes( String aFilename ) throws FileIOException
    {
        // Make sure the file exists in our table of files.
        if( !iFileLocatorTable.containsKey( aFilename ) )
        {
            throw new FileIOException( "file_system_fileioexception_not_exist" + aFilename );
        }
        byte[] resultArray;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //String state = "";

        RecordStore recordStore = null;
        try
        {
            FileLocator fl = (FileLocator) ( iFileLocatorTable.get( aFilename ) );
            int recordStoreName = fl.iRecordStoreNames[0];
            recordStore = RecordStore.openRecordStore( RSFS_FILE_PREFIX + recordStoreName, false );
            for( int i = 0; i < fl.iRecordStoreNames.length; i++ )
            {
                if( recordStoreName != fl.iRecordStoreNames[i] )
                {
                    recordStoreName = fl.iRecordStoreNames[i];
                    recordStore.closeRecordStore();
                    recordStore = RecordStore.openRecordStore( RSFS_FILE_PREFIX + recordStoreName, false );
                }
                byte[] nextData = recordStore.getRecord( fl.iRecordIds[i] );
                baos.write( nextData );
            }
            resultArray = baos.toByteArray();
            baos.close();
        }
        catch( RecordStoreException e )
        {
            LOG.debug( "FileSystem getFile (" + aFilename + ") error", e );
            throw new FileIOException( "file_system_recordstoreexception" + ": " + e.getMessage() );
        }
        catch( IOException e )
        {
            throw new FileIOException( "file_system_fileioexception" + e.getMessage() );
        }
        finally
        {
            try
            {
                recordStore.closeRecordStore();
            }
            catch( Exception e )
            {
                LOG.info( "FileSystem getFile error closing", e );
            }

        }
        return resultArray;
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#listFiles()
     */
    public Enumeration /*String*/listFiles()
    {
        LOG.debug( "fileTable is " + iFileLocatorTable );
        return iFileLocatorTable.keys();
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#listFilesOfType(java.lang.String)
     */
    public Enumeration /*String*/listFilesOfType( String aMimeType )
    {
        Enumeration keysEnumeration = iFileLocatorTable.keys();
        Vector keysVector = new Vector();
        while( keysEnumeration.hasMoreElements() )
        {
            // Don't bother casting to a String as it's not necessary
            final Object key = keysEnumeration.nextElement();
            final FileLocator fl = (FileLocator) iFileLocatorTable.get( key );
            if( fl.iMimeType.equals( aMimeType ) )
            {
                keysVector.addElement( key );
            }
        }
        return keysVector.elements();
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#listFilesInDirectory(java.lang.String)
     */
    public Enumeration /*String*/listFilesInDirectory( String aDirectoryPathEndingInForwardSlash )
    {
        Enumeration keysEnumeration = iFileLocatorTable.keys();

        // We use a Hashtable only as a lazy way to avoid duplicates
        // when we add multiple subdirectories to the list.
        Hashtable directoryList = new Hashtable();

        while( keysEnumeration.hasMoreElements() )
        {
            final String fileName = (String) keysEnumeration.nextElement();

            // 'key' might be something like 'file:////account1/folder1/email1/subject'
            // 'aDirectoryPathEndingInForwardSlash' might be something like 'file:////account1/folder1/'

            if( fileName.startsWith( aDirectoryPathEndingInForwardSlash ) )
            {
                // We know the current key indicates a file under the specified folder, but
                // is it immediately under or in a subfolder?
                // We're only interested in items (both files and subdirectories) directly under 
                // the specified directory path.

                int firstSeparatorAfterDirectoryPath = fileName.indexOf( PATH_DELIMITER, aDirectoryPathEndingInForwardSlash.length() );
                if( -1 != firstSeparatorAfterDirectoryPath )
                {
                    // There are further subdirectories involved.
                    // Strip off everything after the next '/' -- we just want to indicate presence of immediate subdirectory.
                    String subdirectory = fileName.substring( 0, firstSeparatorAfterDirectoryPath + 1 /* to include the final slash */);

                    // Using Hashtable will ensure that duplicates (when there are multiple items
                    // under a subdirectory) get taken care of automatically.
                    directoryList.put( subdirectory, subdirectory /*really just a dummy object */);
                }
                else
                {
                    // There are no further directories involved -- just put this 
                    directoryList.put( fileName, fileName /*really just a dummy object */);
                }
            }
        }
        return directoryList.keys();
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#deleteFilesUnderDirectory(java.lang.String)
     */
    public void deleteFilesUnderDirectory( String aDirectoryPathEndingInForwardSlash )
    {
        Enumeration keysEnumeration = iFileLocatorTable.keys();

        while( keysEnumeration.hasMoreElements() )
        {
            final String fileName = (String) keysEnumeration.nextElement();

            // 'key' might be something like 'file:////account1/folder1/email1/subject'
            // 'aDirectoryPathEndingInForwardSlash' might be something like 'file:////account1/folder1/'

            if( fileName.startsWith( aDirectoryPathEndingInForwardSlash ) )
            {
                try
                {
                    deleteFile( fileName );
                }
                catch( FileIOException e )
                {
                    LOG.info( "Could not delete file " + fileName, e );
                }
            }
        }
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#containsFile(java.lang.String)
     */
    public boolean exists( String aFilename )
    {
        if( iFileLocatorTable != null )
        {
            return iFileLocatorTable.containsKey( aFilename );
        }
        else
        {
            LOG.info( "fileTable was null!!" );
            return false;
        }
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#renameFile(java.lang.String, java.lang.String)
     */
    public void renameFile( String aOriginalName, String aNewName ) throws FileIOException
    {
        if( iFileLocatorTable.containsKey( aOriginalName ) )
        {
            final FileLocator fl = (FileLocator) iFileLocatorTable.get( aOriginalName );
            final FileLocator newFl = new FileLocator( aNewName, fl.iMimeType, fl.iSize, fl.iRecordStoreNames, fl.iRecordIds );
            iFileLocatorTable.remove( aOriginalName );
            iFileLocatorTable.put( aNewName, newFl );

            writeFileTableToRMS();
        }
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#deleteFile(java.lang.String)
     */
    public void deleteFile( String aFilename ) throws FileIOException
    {
        // Make sure the file exists in our table of files.
        if( !iFileLocatorTable.containsKey( aFilename ) )
        {
            throw new FileIOException( "file_system_fileioexception_not_exist " + aFilename );
        }
        try
        {
            FileLocator fl = (FileLocator) ( iFileLocatorTable.get( aFilename ) );

            deleteFileLocatorContents( fl );

            iFileLocatorTable.remove( aFilename );
            writeFileTableToRMS();
        }
        catch( RecordStoreException e )
        {
            LOG.debug( "FileSystem deleteFile (" + aFilename + ") error", e );
            throw new FileIOException( e.getMessage() );
        }
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#deleteFilesOfType(java.lang.String)
     */
    public void deleteFilesOfType( String aMimeType )
    {
        Enumeration deleteList = listFilesOfType( aMimeType );
        while( deleteList.hasMoreElements() )
        {
            String fileToDelete = (String) deleteList.nextElement();
            try
            {
                deleteFile( fileToDelete );
            }
            catch( FileIOException e )
            {
                LOG.info( "Could not delete file " + fileToDelete, e );
            }

        }
    }

    /* (non-Javadoc)
     * @see com.substanceofcode.data.midp.FileSystem#formatFileSystem()
     */
    public int formatFileSystem() throws FileIOException
    {
        try
        {
            String[] stores = RecordStore.listRecordStores();
            for( int i = 0; i < stores.length; i++ )
            {
                deleteRecordStoreClosingIfNeeded( stores[i], null );
            }
            iFileLocatorTable = new Hashtable();
            iCurrentRecordStoreNumber = 1;
            return writeFileTableToRMS();
        }
        catch( Exception e )
        {
            LOG.info( "formatFileSystem()", e );
            throw new FileIOException( e.getMessage() );
        }
    }

    /*
     * PRIVATE METHODS
     */

    private FileSystemImpl()
    {
        try
        {
            RecordStore indexRS = RecordStore.openRecordStore( RSFS_DIRECTORY_PREFIX + 1, false );
            // Records exist already.
            RecordEnumeration re = indexRS.enumerateRecords( null, null, false );
            if( re.hasNextElement() )
            {
                byte[] data = re.nextRecord();
                DataInputStream dos = new DataInputStream( new ByteArrayInputStream( data ) );
                int numberOfFiles = dos.readInt();
                iCurrentRecordStoreNumber = dos.readInt();
                iFileLocatorTable = new Hashtable( numberOfFiles );
                String key;
                FileLocator fl;
                for( int i = 0; i < numberOfFiles; i++ )
                {
                    key = dos.readUTF();
                    fl = new FileLocator( dos );
                    iFileLocatorTable.put( key, fl );
                }
                indexRS.closeRecordStore();
                dos.close();
            }
        }
        catch( RecordStoreNotFoundException e )
        {
            // Records DO NOT exist already
            LOG.debug( "FileSystem index doesn not already exist" );
            iCurrentRecordStoreNumber = 1;
            iFileLocatorTable = new Hashtable();
        }
        catch( RecordStoreException e )
        {
            //either of these exceptions will mean the fileTable
            //is not initialized, causing problems later
            LOG.info( "FileSystem construction RSError", e );
        }
        catch( Exception e )
        {
            LOG.info( "FileSystem construction error", e );
        }
    }

    /**
     * Attempts to delete a RecordStore, retrying after closing if there is a problems.
     * @param aRecordStoreName the name of the RecordStore (must be non-null)
     * @param aRecordStore the handle to the RecordStore (can be null)
     * @throws RecordStoreException
     */
    private static void deleteRecordStoreClosingIfNeeded( String aRecordStoreName, RecordStore aRecordStore )
    {
        try
        {
            RecordStore.deleteRecordStore( aRecordStoreName );
            LOG.debug( "deleteRecordStoreClosingIfNeeded deleted: " + aRecordStoreName );
        }
        catch( Exception e )
        {
            LOG.info( "deleteRecordStoreClosingIfNeeded -- likely still open, trying to close", e );

            try
            {
                // Strange but true -- open it first to get a handle we can use to close.
                if( null == aRecordStore )
                {
                    aRecordStore = RecordStore.openRecordStore( aRecordStoreName, false );
                }
                reallyCloseRecordStore( aRecordStore );

                // Now try.
                RecordStore.deleteRecordStore( aRecordStoreName );
                LOG.debug( "deleteRecordStoreClosingIfNeeded second chance deleted: " + aRecordStoreName );
            }
            catch( Exception secondChange )
            {
                LOG.info( "deleteRecordStoreClosingIfNeeded -- still failed after trying to close", e );
            }
        }
    }

    /**
     * According to the documentation, a RecordStore isn't closed down until all calls to openRecordStore
     * have been matched with calls to closeRecordStore.
     * 
     * To ensure that in cases where we want to close in order to do something like clear,
     * we repeat the close call until it throws a RecordStoreNotOpenException exception
     * or exceeds a max number of attempts.
     * 
     * @param aRecordStore
     * @throws RecordStoreException
     */
    private static void reallyCloseRecordStore( RecordStore aRecordStore ) throws RecordStoreException
    {
        int attempts = 0;
        try
        {
            for( attempts = 0; attempts < MAX_CLOSE_ATTEMPTS; ++attempts )
            {
                aRecordStore.closeRecordStore();
            }
            LOG.info( "reallyCloseRecordStore was not closed after " + attempts + " attempts" );
        }
        catch( RecordStoreNotOpenException rsnoe )
        {
            LOG.debug( "reallyCloseRecordStore closed after attempts: " + attempts );
        }
    }

    /**
     * 
     * @return number of RecordStore's created.
     */
    private int writeFileTableToRMS() throws FileIOException
    {
        RecordStore indexRS = null;
        int recordStoresCreated = 0;

        try
        {

            try
            {
                // First see if the RMS_FILE_INDEX RecordStore already exists.
                indexRS = RecordStore.openRecordStore( RSFS_DIRECTORY_PREFIX + 1, false );

                // If we got to this point, looks like it already exists.

                // Delete all current records, (to be replaced later in this method.
                RecordEnumeration re = indexRS.enumerateRecords( null, null, false );
                while( re.hasNextElement() )
                {
                    indexRS.deleteRecord( re.nextRecordId() );
                }

            }
            catch( RecordStoreNotFoundException rsnfe )
            {
                // Looks like the RMS_FILE_INDEX didn't already exist.

                // Try creating.
                indexRS = RecordStore.openRecordStore( RSFS_DIRECTORY_PREFIX + 1, true );
                ++recordStoresCreated;
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream( baos );
            //int numberOfFiles
            dos.writeInt( iFileLocatorTable.size() );
            //currentRecordStoreNumber
            dos.writeInt( iCurrentRecordStoreNumber );

            Enumeration keys = iFileLocatorTable.keys();
            for( int i = 0; i < iFileLocatorTable.size(); i++ )
            {
                String currentKey = (String) keys.nextElement();
                dos.writeUTF( currentKey );
                FileLocator fl = (FileLocator) iFileLocatorTable.get( currentKey );
                fl.writeFileLocator( dos );
                dos.flush();
            }
            byte[] data = baos.toByteArray();
            indexRS.addRecord( data, 0, data.length );
            dos.close();
            baos.close();
        }
        catch( RecordStoreException e )
        {
            LOG.info( "writeFileTableToRMS() RSE", e );
            throw new FileIOException( "file_system_error_rms" );
        }
        catch( IOException e )
        {
            LOG.info( "writeFileTableToRMS() IOE", e );
            throw new FileIOException( "file_system_error_rms" );
        }
        finally
        {
            if( null != indexRS )
            {
                try
                {
                    indexRS.closeRecordStore();
                }
                catch( RecordStoreException e )
                {
                    LOG.info( "writeFileTableToRMS() RSE", e );
                }
            }
        }

        return recordStoresCreated;
    }

    private void deleteFileLocatorContents( FileLocator aFileLocator ) throws RecordStoreException
    {

        //   Logger.debug("FileSystem: fl="+fl +"\n aFilename="+aFilename+"\n fl.recordStores.length="+fl.recordStores.length );
        if( aFileLocator.iRecordStoreNames.length > 0 )
        {
            int recordStoreName = aFileLocator.iRecordStoreNames[0];
            RecordStore recordStore = null;
            try
            {
                recordStore = RecordStore.openRecordStore( RSFS_FILE_PREFIX + recordStoreName, false );
                for( int i = 0; i < aFileLocator.iRecordStoreNames.length; i++ )
                {
                    if( recordStoreName != aFileLocator.iRecordStoreNames[i] )
                    {
                        recordStoreName = aFileLocator.iRecordStoreNames[i];
                        recordStore.closeRecordStore();
                        recordStore = RecordStore.openRecordStore( RSFS_FILE_PREFIX + recordStoreName, false );
                    }
                    recordStore.deleteRecord( aFileLocator.iRecordIds[i] );
                }
                if( recordStore.getSize() == 0 )
                {
                    deleteRecordStoreClosingIfNeeded( RSFS_FILE_PREFIX + recordStoreName, recordStore );
                    recordStore = null;
                }
            }
            finally
            {
                if( null != recordStore )
                {
                    recordStore.closeRecordStore();
                }
            }
        }
    }

    /**
     * Path delimiter used internally -- we conform to JSR-75 FileConnection in always using 
     * forward slash '/' regardless of underlying platform's file.separator.
     */
    private static final String                           PATH_DELIMITER                   = "/";

    private static FileSystemImpl                         iFileSystem;

    private static Logger                                 LOG;

    /**
     * Used in reallyCloseRecordStore(). 
     * 
     * TODO: Could such a high max close attempts be why things thrashed for a while 
     * in old system when there was a problem?
     */
    private static final int                              MAX_CLOSE_ATTEMPTS               = 100;

    /**
     * RecordStores used to store directory information.
     */
    /* package private for testing */static final String RSFS_DIRECTORY_PREFIX            = "rsfsd";

    /**
     * RecordStores used to store actual 'files'.
     */
    /* package private for testing */static final String RSFS_FILE_PREFIX                 = "rsfsf";

    /**
     * A hashtable from 'String' (filename) to 'Filelocator'.
     */
    private Hashtable                                     iFileLocatorTable;

    /**
     * We use numbers as names for new RecordStores we fill up. This is which RecordStore we
     * are currently filling.
     */
    /* package private for testing */int                 iCurrentRecordStoreNumber;

    private static final int                              DEFAULT_RMS_RECORD_SIZE          = 50000;
    private static final int                              DEFAULT_RMS_NUMBER_RECORDS       = 150000;
    /* package private for testing */static final int    DEFAULT_RMS_NUMBER_RECORD_STORES = 4;

    private int                                           iMaxRecordSize                   = DEFAULT_RMS_RECORD_SIZE;
    private int                                           iMaxRecordsPerRecordStore        = DEFAULT_RMS_NUMBER_RECORDS;
    private int                                           iMaxRecordStores                 = DEFAULT_RMS_NUMBER_RECORD_STORES;

}